#pragma once

#include "DrawingContext.h"
#include <string>
#include "WindowClass.h"
#include "Controls/SampleOverlay.h"
#include <windowsx.h>
#include <dwmapi.h>

#pragma comment(lib, "dwmapi")

namespace WinDirectUI
{
	using std::wstring;

	class Window
	{
		PUBLIC Window()
		{
			m_title = L"WinDirectUIWindow";
			m_drawingContext = nullptr;
			m_windowHandle = nullptr;
			m_windowSize = D2D1_SIZE_F{
				.width = 960,
				.height = 594,
			};
			m_frameSize = D2D1_SIZE_F{
				.width = m_windowSize.width + FrameWidth,
				.height = m_windowSize.height + FrameWidth
			};

			m_windowCornerRadius = 30;

			m_sizeBoxRect = D2D1_ROUNDED_RECT{
				.rect = D2D1_RECT_F{
					.left = (FrameWidth - m_sizeBoxWidth),
					.top = (FrameWidth - m_sizeBoxWidth),
					.right = m_frameSize.width - FrameWidth + m_sizeBoxWidth,
					.bottom = m_frameSize.height - FrameWidth + m_sizeBoxWidth
				},
				// (m_windowCornerRadius == 0 ? 0 : m_sizeBoxWidth) 圆角为0，SIZEBOX 框不应该存在圆角
				.radiusX = (float)m_windowCornerRadius + (m_windowCornerRadius == 0 ? 0 : m_sizeBoxWidth),
				.radiusY = (float)m_windowCornerRadius + (m_windowCornerRadius == 0 ? 0 : m_sizeBoxWidth),
			};

			m_backgroundColor = D2D1_COLOR_F{
				.r = 1,
				.g = 1,
				.b = 1,
				.a = 1
			};

			CreateMyWindow();
		}

		PRIVATE wstring m_title;
		PRIVATE HWND m_windowHandle;
		PRIVATE DrawingContext* m_drawingContext;

		/// <summary>
		/// 窗口有两层
		/// 1、框体层，用来包含渲染的窗口阴影，处理窗口大小改变的 SIZEBOX 框
		/// （1）这是 windows 窗口的尺寸，用来呈现整个窗口客户区，阴影，SIZEBOX
		/// （2）使用 m_frameSize 来表示整个窗口的尺寸
		/// （4）创建窗口时，需要计算 m_frameSize 的尺寸，公式：m_frameSize = m_ligicWindowSize + FrameWidth
		/// （5）使用 FrameWidth 窗口框体，该区域用来呈现窗口阴影和 SIZEBOX
		/// （6）使用 m_sizeBoxWidth 来表示 SIZEBOX 的线条宽度
		/// （7）在 SIZEBOX 外的区域会命中穿透，使用 m_sizeBoxGeometry 做命中测试
		/// </summary>
		PRIVATE D2D1_SIZE_F m_frameSize;
		PRIVATE const float FrameWidth = 27;
		PRIVATE float m_sizeBoxWidth = 7;
		PRIVATE D2D1_ROUNDED_RECT m_sizeBoxRect;
		PRIVATE ComPtr<ID2D1GeometryGroup> m_m_sizeBoxWidthGeometry;
		PRIVATE ComPtr<ID2D1RoundedRectangleGeometry> m_sizeBoxGeometry;

		/// <summary>
		/// 创建 SizeBox 的框体
		/// 窗体大小改变是，应该调用该方法
		/// </summary>
		PRIVATE void CreateSizeBoxGeometry()
		{
			m_m_sizeBoxWidthGeometry = nullptr;
			m_sizeBoxGeometry = nullptr;

			auto factory = m_drawingContext->GetD2DFactory();

			ThrowIfFailed(
				factory->CreateRoundedRectangleGeometry(
					m_sizeBoxRect, m_sizeBoxGeometry.GetAddressOf())
			);

			auto rect = m_sizeBoxRect.rect;
			D2D1_ROUNDED_RECT sizeBox1{
				.rect = D2D1_RECT_F
				{
					.left = rect.left + m_sizeBoxWidth,
					.top = rect.top + m_sizeBoxWidth,
					.right = rect.right - m_sizeBoxWidth,
					.bottom = rect.bottom - m_sizeBoxWidth
				},
				.radiusX = (float)m_windowCornerRadius,
				.radiusY = (float)m_windowCornerRadius
			};
			ComPtr<ID2D1RoundedRectangleGeometry> g2;
			ThrowIfFailed(
				factory->CreateRoundedRectangleGeometry(
					sizeBox1, g2.GetAddressOf())
			);

			ID2D1Geometry* group[2] = { m_sizeBoxGeometry.Get(), g2.Get() };

			ThrowIfFailed(
				factory->CreateGeometryGroup(
					D2D1_FILL_MODE::D2D1_FILL_MODE_ALTERNATE,
					group, 2,
					m_m_sizeBoxWidthGeometry.GetAddressOf())
			);

			g2 = nullptr;
		}

		/// <summary>
		/// 1、窗口内容呈现层、这里才是我们用户呈现的区域
		/// （1）这一层，我们把它命名为逻辑窗口，因为它才是用户可见的窗口
		/// （2）它的尺寸用 m_windowSize 来表示
		/// （3）使用 m_windowCornerRadius 来设置我们窗口的圆角
		/// （4）这一层会绘制窗口阴影
		/// （5）使用 m_shadowEffect 来绘制窗口阴影
		/// </summary>
		PRIVATE D2D1_SIZE_F m_windowSize;
		PRIVATE int m_windowCornerRadius;
		PRIVATE ComPtr<ID2D1SolidColorBrush> m_shadowSolidBrush;
		PRIVATE D2D1_COLOR_F m_backgroundColor;
		PRIVATE ComPtr<ID2D1Bitmap1> m_shadowBitmap;
		PRIVATE ComPtr<ID2D1Effect> m_shadowEffect;

		PRIVATE DWORD m_currentWindowExStyle = WS_EX_NOREDIRECTIONBITMAP;

		/// <summary>
		/// 获取窗口句柄
		/// </summary>
		PUBLIC HWND GetWindowHandle()
		{
			return m_windowHandle;
		}

		/// <summary>
		/// 绘制动态的阴影背景
		/// </summary>
		PRIVATE void DrawWindowSizeDependentShadowEffect()
		{
			m_shadowBitmap = nullptr;
			m_shadowEffect = nullptr;

			ID2D1DeviceContext* dc = m_drawingContext->GetD2DDeviceContext();

			D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
				D2D1_ALPHA_MODE::D2D1_ALPHA_MODE_PREMULTIPLIED);
			auto size = D2D1_SIZE_U((UINT32)m_windowSize.width, (UINT32)m_windowSize.height);

			D2D1_BITMAP_PROPERTIES1 bitmapProp = {
				.pixelFormat = pixelFormat,
				.dpiX = m_drawingContext->GetDpi(),
				.dpiY = m_drawingContext->GetDpi(),
				.bitmapOptions = D2D1_BITMAP_OPTIONS_NONE | D2D1_BITMAP_OPTIONS_TARGET
			};

			ThrowIfFailed(
				// 未指定数据源，所以不用设置间距
				dc->CreateBitmap(size, nullptr, 0, &bitmapProp, m_shadowBitmap.GetAddressOf())
			);

			ComPtr<ID2D1Image> oldImgae;
			dc->GetTarget(oldImgae.GetAddressOf());
			dc->SetTarget(m_shadowBitmap.Get());
			dc->BeginDraw();

			dc->FillRoundedRectangle(D2D1_ROUNDED_RECT{
				.rect = D2D1_RECT_F{
					.left = 0,
					.top = 0,
					.right = m_windowSize.width,
					.bottom = m_windowSize.height },
				.radiusX = (float)m_windowCornerRadius,
				.radiusY = (float)m_windowCornerRadius,
				}, m_shadowSolidBrush.Get());

			ThrowIfFailed(
				dc->EndDraw());
			dc->SetTarget(oldImgae.Get());

			auto bitmap = m_shadowBitmap.Get();

			ComPtr<ID2D1Effect> shadowEffect;
			dc->CreateEffect(CLSID_D2D1Shadow, &shadowEffect);

			shadowEffect->SetInput(0, bitmap);

			dc->CreateEffect(CLSID_D2D1Composite, &m_shadowEffect);

			m_shadowEffect->SetInputEffect(0, shadowEffect.Get());
			m_shadowEffect->SetInput(1, bitmap);
		}

		/// <summary>
		/// 绘制窗口内容
		/// </summary>
		PRIVATE void OnRender()
		{
			ID2D1DeviceContext* dc = m_drawingContext->GetD2DDeviceContext();
		}

		/// <summary>
		/// 窗口背景
		/// </summary>
		PRIVATE void DrawWindowBackground()
		{
			ID2D1DeviceContext* dc = m_drawingContext->GetD2DDeviceContext();
			dc->BeginDraw();
			// 清理残留，一定要清理，否则阴影会失效
			// nullptr 表示透明黑色
			dc->Clear(nullptr);
			// 绘制阴影背景
			dc->DrawImage(m_shadowEffect.Get(), GetClientStartPoint());
			ThrowIfFailed(
				dc->EndDraw()
			);
		}

		/// <summary>
		/// 创建用户窗口
		/// </summary>
		PRIVATE HWND CreateMyWindow()
		{
			auto desktopHandle = ::GetDesktopWindow();
			RECT rect;
			GetWindowRect(desktopHandle, &rect);
			auto left = (rect.right - m_frameSize.width) / 2;
			auto top = (rect.bottom - m_frameSize.height) / 2;
			auto windowClass = WindowClass::Get();

			return ::CreateWindowEx(
				m_currentWindowExStyle,
				windowClass->lpszClassName,
				m_title.c_str(),
				WS_OVERLAPPEDWINDOW,
				left, // x
				top, // y
				m_frameSize.width, // width
				m_frameSize.height, // height
				nullptr, //parent
				nullptr, // menu
				windowClass->hInstance,
				this);
		}

		// Hit test the frame for resizing and moving.
		LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
		{
			// Get the point coordinates for the hit test.
			POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			ThrowIfFalse(::ScreenToClient(hWnd, &ptMouse), "屏幕坐标转换客户区坐标失败！");

			// Determine if the hit test is for resizing. Default middle (1,1).
			USHORT uRow = 1;
			USHORT uCol = 1;

			BOOL contains;
			m_m_sizeBoxWidthGeometry->FillContainsPoint(D2D1_POINT_2F{
				.x = (float)ptMouse.x,
				.y = (float)ptMouse.y
				},
				D2D1::Matrix3x2F::Identity(), &contains);

			// 鼠标移动到 SIZEBOX 才处理坐标运算
			if (contains)
			{
				if (ptMouse.x >= FrameWidth - m_sizeBoxWidth
					&& ptMouse.x < FrameWidth + m_windowCornerRadius)
				{
					// 鼠标落在左侧区域
					uCol = 0;

					if (ptMouse.y > FrameWidth + m_windowCornerRadius
						&& ptMouse.y < m_frameSize.height - FrameWidth - m_windowCornerRadius)
					{
						// 鼠标落在左侧边框
						uRow = 1;
					}

					if (ptMouse.y >= FrameWidth - m_sizeBoxWidth
						&& ptMouse.y <= FrameWidth + m_windowCornerRadius)
					{
						// 鼠标落在左上角圆角
						uRow = 0;
					}
					else if (ptMouse.y >= m_frameSize.height - FrameWidth - m_windowCornerRadius
						&& ptMouse.y <= m_frameSize.height - FrameWidth + m_sizeBoxWidth)
					{
						// 鼠标落左下角
						uRow = 2;
					}
				}
				else if (ptMouse.x >= FrameWidth + m_windowCornerRadius
					&& ptMouse.x <= m_frameSize.width - FrameWidth - m_windowCornerRadius)
				{
					// 鼠标落在中间区域
					uCol = 1;

					if (ptMouse.y < FrameWidth)
					{
						// 鼠标落在顶部边框
						uRow = 0;
					}
					else if (ptMouse.y > m_frameSize.height - FrameWidth)
					{
						// 鼠标落在底部边框
						uRow = 2;
					}

				}
				else if (ptMouse.x > m_frameSize.width - FrameWidth - m_windowCornerRadius
					&& ptMouse.x <= m_frameSize.width - FrameWidth + m_sizeBoxWidth)
				{
					// 鼠标在右侧区域
					uCol = 2;

					if (ptMouse.y > FrameWidth + m_windowCornerRadius
						&& ptMouse.y < m_frameSize.height - FrameWidth - m_windowCornerRadius)
					{
						// 鼠标落在由侧边框
						uRow = 1;
					}

					if (ptMouse.y >= FrameWidth - m_sizeBoxWidth
						&& ptMouse.y <= FrameWidth + m_windowCornerRadius)
					{
						// 鼠标落在右上角圆角
						uRow = 0;
					}
					else if (ptMouse.y >= m_frameSize.height - FrameWidth - m_windowCornerRadius
						&& ptMouse.y <= m_frameSize.height - FrameWidth + m_sizeBoxWidth)
					{
						// 鼠标落右下角
						uRow = 2;
					}
				}
			}

			LRESULT hitTests[3][3] =
			{
				{ HTTOPLEFT,    HTTOP,    HTTOPRIGHT },
				{ HTLEFT,       HTNOWHERE,     HTRIGHT },
				{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
			};

			return hitTests[uRow][uCol];
		}

		// 删除 DWM 标准帧
		PRIVATE void DeleteStandardFrame(HWND hWnd)
		{
			RECT rcClient;
			GetWindowRect(hWnd, &rcClient);

			// Inform the application of the frame change.
			SetWindowPos(hWnd,
				NULL,
				rcClient.left, rcClient.top,
				RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
				SWP_FRAMECHANGED);
		}

		/// <summary>
		/// 窗口过程
		/// </summary>
		PUBLIC LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
		{
			LRESULT lResult = 0;
			handled = false;

			if (message == WM_PAINT)
			{
				OnPaint();
				lResult = 0;
				handled = true;
			}
			else if (message == WM_SIZE)
			{
				OnWindowSizeChanged();
				lResult = 0;
				return true;
			}
			else if (message == WM_NCHITTEST)
			{
				lResult = HitTestNCA(hWnd, wParam, lParam);
				handled = true;
				return lResult;
			}
			else if (message == WM_CREATE)
			{
				OnWindowCreated(hWnd);
				lResult = 0;
				handled = true;
			}
			else if (message == WM_NCCALCSIZE && wParam == true)
			{
				handled = true;
				// 当 lResult = 0，会删除标准框架（Frame），
				// 不过应用程序启动时，不会触发 WM_NCCALCSIZE 消息，
				// 所以应该调用 DeleteStandardFrame 方法设置客户区的大小，
				// 且触发 WM_NCCALCSIZE 消息，返回 0 之后，标准帧即删除完成
				return lResult;
			}

			return lResult;
		}

		/// <summary>
		/// 窗口尺寸改变调用
		/// </summary>
		PRIVATE void OnWindowSizeChanged()
		{
			RECT clientRect;
			GetClientRect(m_windowHandle, &clientRect);

			// 更新外部边框尺寸
			m_frameSize.width = RECTWIDTH(clientRect);
			m_frameSize.height = RECTHEIGHT(clientRect);

			// 更新窗口尺寸
			// 因为边框有上下左右，所以要乘以 2
			m_windowSize.width = max(m_frameSize.width - FrameWidth * 2, 1);
			m_windowSize.height = max(m_frameSize.height - FrameWidth * 2, 1);

			m_sizeBoxRect = D2D1_ROUNDED_RECT{
				.rect = D2D1_RECT_F{
					.left = (FrameWidth - m_sizeBoxWidth),
					.top = (FrameWidth - m_sizeBoxWidth),
					.right = m_frameSize.width - FrameWidth + m_sizeBoxWidth,
					.bottom = m_frameSize.height - FrameWidth + m_sizeBoxWidth
				},
				.radiusX = (float)m_windowCornerRadius + (m_windowCornerRadius == 0 ? 0 : m_sizeBoxWidth),
				.radiusY = (float)m_windowCornerRadius + (m_windowCornerRadius == 0 ? 0 : m_sizeBoxWidth),
			};
			// 注意这三个的调用顺序
			// CreateWindowSizeDependentResources 可能会重新初始化 3D 2D 设备
			m_drawingContext->CreateWindowSizeDependentResources();
			// 创建边框相关资源
			CreateSizeBoxGeometry();
			// 创建背景阴影效果
			DrawWindowSizeDependentShadowEffect();
		}

		/// <summary>
		/// 当窗口创建后调用
		/// </summary>
		PRIVATE void OnWindowCreated(HWND hWnd)
		{
			m_windowHandle = hWnd;
			m_drawingContext = new DrawingContext(hWnd);

			// 因为我们要删除标准框体，所以用窗口大小来设置客户区大小
			RECT clientRect;
			GetWindowRect(m_windowHandle, &clientRect);

			m_windowSize.width = (float)max(RECTWIDTH(clientRect) - FrameWidth, 1);
			m_windowSize.height = (float)max(RECTHEIGHT(clientRect) - FrameWidth, 1);

			ThrowIfFailed(
				m_drawingContext->GetD2DDeviceContext()
				->CreateSolidColorBrush(m_backgroundColor, m_shadowSolidBrush.GetAddressOf())
			);

			// Get the frame rectangle, adjusted for the style without a caption.
			RECT rcFrame = { 0 };

			ThrowIfFalse(
				AdjustWindowRectExForDpi(
					&rcFrame,
					WS_OVERLAPPEDWINDOW & ~WS_CAPTION,
					FALSE,
					NULL,
					(UINT)m_drawingContext->GetDpi()),
				"不能获取窗口边框尺寸."
			);

			auto maxHorization = max(abs(rcFrame.left), abs(rcFrame.right));
			m_sizeBoxWidth = max(maxHorization, max(abs(rcFrame.top), abs(rcFrame.bottom)));
			DeleteStandardFrame(hWnd);
		}

		/// <summary>
		/// 显示窗口
		/// </summary>
		PUBLIC void Show()
		{
			::ShowWindow(m_windowHandle, SW_SHOW);
			::UpdateWindow(m_windowHandle);
		}

		/// <summary>
		/// 当窗口渲染时调用
		/// </summary>
		PROTECTED void OnPaint()
		{
			// 更新命中穿透
			UpdateHitPenetration();
			m_drawingContext->CheckDirectCompositionDeviceState();
			// 开始绘制窗口背景
			DrawWindowBackground();
			OnRender();
			// 内容呈现
			m_drawingContext->Present();
		}

		// 更新命中穿透
		PRIVATE void UpdateHitPenetration()
		{
			POINT clientPoint;
			ThrowIfFalse(::GetCursorPos(&clientPoint), "获取鼠标坐标失败！");
			ThrowIfFalse(::ScreenToClient(m_windowHandle, &clientPoint), "坐标转换失败！");

			BOOL isContainer = false;
			ThrowIfFailed(
				m_sizeBoxGeometry->FillContainsPoint(
					D2D1_POINT_2F{
						.x = (float)clientPoint.x,
						.y = (float)clientPoint.y,
					},
					D2D1::Matrix3x2F::Identity(),
					&isContainer));

			if (isContainer)
			{
				UnuseHitPenetration();
			}
			else {
				UseHitPenetration();
			}
		}

		// 使用命中穿透
		PRIVATE const DWORD HitPenetrationStyle = WS_EX_NOREDIRECTIONBITMAP | WS_EX_LAYERED | WS_EX_TRANSPARENT;
		PRIVATE void UseHitPenetration()
		{
			if (m_currentWindowExStyle != HitPenetrationStyle)
			{
				m_currentWindowExStyle = HitPenetrationStyle;
				// 设置穿透
				::SetWindowLong(m_windowHandle, GWL_EXSTYLE, m_currentWindowExStyle);
				::SetLayeredWindowAttributes(m_windowHandle, 0, 255, LWA_ALPHA);
			}
		}

		// 隐藏命中穿透
		PRIVATE void UnuseHitPenetration()
		{
			if (m_currentWindowExStyle != WS_EX_NOREDIRECTIONBITMAP)
			{
				m_currentWindowExStyle = WS_EX_NOREDIRECTIONBITMAP;
				// 设置穿透
				::SetWindowLong(m_windowHandle, GWL_EXSTYLE, m_currentWindowExStyle);
			}
		}

		/// <summary>
		/// 获取客户区的起始点
		/// </summary>
		PRIVATE D2D1_POINT_2F GetClientStartPoint()
		{
			return D2D1_POINT_2F{
				.x = FrameWidth,
				.y = FrameWidth
			};
		}
	};
}